/*
 * Copyright 2007 National Bioinformatics Network
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package dgbgui.gui.shapes;

import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;

public class RoundedRectangleShape implements Shape {
    
    public RoundedRectangleShape(
            double x, double y, 
            double width, double height,
            double topRadius, double bottomRadius) {
        m_bounding = new Rectangle2D.Double(x,y,width,height);
        m_topRadius = topRadius;
        m_bottomRadius = bottomRadius;
    }

    public boolean contains(Point2D arg0) {
        return m_bounding.contains(arg0);
    }

    public boolean contains(Rectangle2D arg0) {
        return m_bounding.contains(arg0);
    }

    public boolean contains(double arg0, double arg1) {
        return m_bounding.contains(arg0,arg1);
    }

    public boolean contains(double arg0, double arg1, double arg2, double arg3) {
        return m_bounding.contains(arg0, arg1, arg2, arg3);
    }

    public Rectangle getBounds() {
        return m_bounding.getBounds();
    }

    public Rectangle2D getBounds2D() {
        return m_bounding.getBounds2D();
    }

    public PathIterator getPathIterator(AffineTransform transform) {
        //return m_bounding.getPathIterator(arg0);
        return new RoundedPathIterator(
                m_bounding.getX(),
                m_bounding.getY(),
                m_bounding.getWidth(),
                m_bounding.getHeight(),
                m_topRadius,m_bottomRadius,
                transform);
                
    }

    public PathIterator getPathIterator(AffineTransform transform, double flatness) {
        //return m_bounding.getPathIterator(arg0,arg1);
        return new RoundedPathIterator(
                m_bounding.getX(),
                m_bounding.getY(),
                m_bounding.getWidth(),
                m_bounding.getHeight(),
                m_topRadius,m_bottomRadius,
                transform);
        
    }

    public boolean intersects(Rectangle2D arg0) {
        return m_bounding.intersects(arg0);
    }

    public boolean intersects(double arg0, double arg1, double arg2, double arg3) {
        return m_bounding.intersects(arg0, arg1, arg2, arg3);
    }
    
    // The Rectangle2D.Double that bounds the roucned rectangle
    Rectangle2D.Double m_bounding = new Rectangle2D.Double();
    
    private static class RoundedPathIterator implements PathIterator {
        
        public RoundedPathIterator(
                double x, double y,
                double width, double height,
                double topRadius, double bottomRadius, 
                AffineTransform trans) {
            
            m_trans = trans;

            double x1 = x;
            double y1 = y;
            double x2 = x + width;
            double y2 = y + height;

            assert ( width >= 0 );
            assert ( height >= 0 );

            // This is an approximate number of points we need to make a smooth
            // surface on a quater of the ellipse
            int nTopCurvePoints = (int)topRadius*2;
            int nBottomCurvePoints = (int)bottomRadius*2;

            // The total number of point = 
            // 2 * nTopCurvePoints
            // + 2* nBottomCurvePoints
            // + 8 points for the 4 lines
            // A last point for the end

            m_numValues = 2*(2*nTopCurvePoints + 2*nBottomCurvePoints + 8 +1);
            m_geometry = new double[m_numValues];

            int pointIndex = 0;
            
            // Left line
            m_geometry[pointIndex++] = x1; m_geometry[pointIndex++] = y1+topRadius;
            m_geometry[pointIndex++] = x1; m_geometry[pointIndex++] = y2-bottomRadius;
            
            // Bottom left arc
            for ( int curvePoint = 1; curvePoint <= nBottomCurvePoints; ++curvePoint ) {
                double angle = ((double)(curvePoint))/(nBottomCurvePoints+1)*(Math.PI/2);
                double rx = bottomRadius* Math.cos(angle);
                double ry = bottomRadius* Math.sin(angle);
                m_geometry[pointIndex++] = x1 + bottomRadius - rx;
                m_geometry[pointIndex++] = y2 - bottomRadius + ry;
            }
            
            // Bottom line
            m_geometry[pointIndex++] = x1 + bottomRadius; m_geometry[pointIndex++] = y2;
            m_geometry[pointIndex++] = x2 - bottomRadius; m_geometry[pointIndex++] = y2;
            
            // Bottom right
            for ( int curvePoint = 1; curvePoint <= nBottomCurvePoints; ++curvePoint ) {
                double angle = ((double)(curvePoint))/(nBottomCurvePoints+1)*(Math.PI/2);
                double ry = bottomRadius* Math.cos(angle);
                double rx = bottomRadius* Math.sin(angle);

                m_geometry[pointIndex++] = x2 - bottomRadius + rx;
                m_geometry[pointIndex++] = y2 - bottomRadius + ry;

            }
            
            // Right line
            m_geometry[pointIndex++] = x2; m_geometry[pointIndex++] = y2 - bottomRadius;
            m_geometry[pointIndex++] = x2; m_geometry[pointIndex++] = y1 + topRadius;
            

            // Top right curve
            for ( int curvePoint = 1; curvePoint <= nTopCurvePoints; ++curvePoint ) {
                double angle = ((double)(curvePoint))/(nTopCurvePoints+1)*(Math.PI/2);
                double rx = topRadius* Math.cos(angle);
                double ry = topRadius* Math.sin(angle);

                m_geometry[pointIndex++] = x2 - topRadius + rx;
                m_geometry[pointIndex++] = y1 + topRadius - ry;
            }

            // Top line
            m_geometry[pointIndex++] = x2-topRadius; m_geometry[pointIndex++] = y1;
            m_geometry[pointIndex++] = x1+topRadius; m_geometry[pointIndex++] = y1;

            // Top left curve
            for ( int curvePoint = 1; curvePoint <= nTopCurvePoints; ++curvePoint ) {
                double angle = ((double)(curvePoint))/(nTopCurvePoints+1)*(Math.PI/2);
                double ry = topRadius* Math.cos(angle);
                double rx = topRadius* Math.sin(angle);
                m_geometry[pointIndex++] = x1 + topRadius - rx;
                m_geometry[pointIndex++] = y1 + topRadius - ry;

            }

            // Close the loop
            m_geometry[pointIndex++] = x1; m_geometry[pointIndex++] = y1+topRadius;
            
        }

        private Point2D.Double currentSegment() {
            Point2D.Double tempPoint = new Point2D.Double(
                    m_geometry[m_currentIndex*2],
                    m_geometry[m_currentIndex*2+1]);
            if (m_trans != null) {
                m_trans.transform(tempPoint, tempPoint);
            }
            return tempPoint;
        }
        
        public int currentSegment(float[] coords) {
            Point2D tempPoint = currentSegment();
            coords[0] = (float)tempPoint.getX();
            coords[1] = (float)tempPoint.getY();
            return (m_currentIndex == 0 ? PathIterator.SEG_MOVETO : PathIterator.SEG_LINETO);
        }

        public int currentSegment(double[] coords) {
            Point2D tempPoint = currentSegment();
            coords[0] = tempPoint.getX();
            coords[1] = tempPoint.getY();
            return (m_currentIndex == 0 ? PathIterator.SEG_MOVETO : PathIterator.SEG_LINETO);
        }

        public int getWindingRule() {
            return GeneralPath.WIND_EVEN_ODD;
        }

        public boolean isDone() {
            return m_currentIndex*2 == m_numValues;
        }

        public void next() {
            ++m_currentIndex;
            
        }
        
        int m_currentIndex = 0;
        int m_numValues = 0;
        double[] m_geometry;
        private AffineTransform m_trans;
    
    }
    
    // The radii
    double m_bottomRadius = 0;
    double m_topRadius = 0;

}
